# -*- coding: UTF-8 -*-

import os, sys
from datetime import date

import pygtk
import gtk
import gtk.gdk
import gtk.glade
import threading, thread, time

import Image
from ImageHelper import AutoOrientImage, FitResize, SetImageTimeStamp, SetEXIF, \
    GetEXIF
from ImageHelper import CropResize
from ImageHelper import GetGtkPixbuf
from ImageHelper import GetImageOrientation
from MediaHelper import GetMediaTimeStamp, GetFileTimeStamp, DefaultOpen, \
    SetFileTimeStamp
from MediaHelper import IsSupportedFile
from MediaHelper import MediaFiles
import datetime
from argparse import ArgumentTypeError


class GtkImageAppBase:

    def __init__(self):
        self._xmlName = "gtkImageAppBase.glade"
        self._xml = gtk.glade.XML(self._xmlName)
        self._window = self._xml.get_widget("window1")
        self._notebook = self._xml.get_widget("notebook1")
        self._thumbTable = self._xml.get_widget("table1")
        self._srcFolder = self._xml.get_widget("filechooserbutton1")
        self._scan = self._xml.get_widget("button1")
        self._scan.connect("clicked", self._onScan)
        self._scan.set_label("Scan")
        self._itemCount = self._xml.get_widget("label3")
        self._itemCount.set_text("")
        self._scanStatus = self._xml.get_widget("label2")
        self._scanStatus.set_text("")
        self._calFrom = self._xml.get_widget("calendar1")
        self._calTo = self._xml.get_widget("calendar2")
        self._calFrom.select_day(date.today().day)
        self._calTo.select_day(date.today().day)
        self._calActive = self._xml.get_widget("checkbutton1")
        self._calActive.connect("toggled", self._updateCalendar)
        self._scanSubfolder = self._xml.get_widget("checkbutton2")
        self._colorPalette = self._xml.get_widget("spinbutton1")
        self._palatteModeWeb = self._xml.get_widget("radiobutton1")
        self._palatteModeAdaptive = self._xml.get_widget("radiobutton2")
        self._colorPalette.set_value(216)
        self._colorPalette.set_sensitive(False)
        self._palatteModeAdaptive.connect("toggled", self._onPalatteModeChanged)
        self._radioBtnFileDate = self._xml.get_widget('radiobutton4')
        self._radioBtnMetaDate = self._xml.get_widget('radiobutton3')
        self._radioBtnMetaDate.set_active(True)
        self._radioBtnFileDate.connect('toggled', self.doUpdateTable)
        self._radioBtnMetaDate.connect('toggled', self.doUpdateTable)
        self._fileSelect = gtk.CheckButton
        self._fileSelect = self._xml.get_widget('checkbutton3')
        self._fileSelect.connect('released', self._onAllFileSelectClicked)
        self._fileSelect.connect('toggled', self._onAllFileSelectToggled)
        self._btnFileProps = gtk.Button
        self._btnFileRemove = gtk.Button
        self._btnFileTurnL = gtk.Button
        self._btnFileTurnR = gtk.Button
        self._btnFileProps = self._xml.get_widget('button2')
        self._btnFileRemove = self._xml.get_widget('button3')
        self._btnFileTurnL = self._xml.get_widget('button4')
        self._btnFileTurnR = self._xml.get_widget('button5')
        self._btnFileProps.set_image(gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU))
        self._btnFileRemove.set_image(gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU))
        self._btnFileTurnL.set_image(gtk.image_new_from_stock(gtk.STOCK_UNDO, gtk.ICON_SIZE_MENU))
        self._btnFileTurnR.set_image(gtk.image_new_from_stock(gtk.STOCK_REDO, gtk.ICON_SIZE_MENU))
        self._btnFileProps.connect('clicked', self._onBtnFilePropClicked)
        self._srcFolderPath = None
        self._scanRunning = False
        self._scanAbort = False
        self._scanCount = 0
        self._filterOn = False
        self._filterFrom = date.min
        self._filterTo = date.max
        self._loading_thread = None
        self._mediaFiles = MediaFiles()
        self._updateCalendar()
        def uri_hook(widget, uri, caller):
            DefaultOpen(uri)
        gtk.link_button_set_uri_hook(uri_hook, self)
    
    def _updateCalendar(self, args=None):
        if self._calActive.get_active():
            self._calFrom.show_all()
            self._calTo.show_all()
        else:
            self._calFrom.hide_all()
            self._calTo.hide_all()
    
    def append(self, gtkChild, gtkLabel):
        self._notebook.append_page(gtkChild, gtkLabel)
        
    def show(self):
        gtk.threads_init()
        if (self._window):
            self._window.connect("destroy", self.quit)
            self._window.show()
        gtk.threads_enter()
        gtk.main()
        gtk.threads_leave()
        
    def getFiles(self):
        '''getFiles() --> returns an instance of class MediaHelper.MediaFiles
        '''
        mf = self._mediaFiles
        return mf
    
    def getRootFolder(self):
        return self._srcFolderPath
    
    def getModeFileDate(self):
        return self._radioBtnFileDate.get_active()
    
    def getPaletteMode(self):
        """getPaletteMode() --> returns the 8-bit color palette mode from PIL:
        Image.WEB: default, standard 216-color "web palette"
        Image.ADAPTIVE: an optimized palette
        """
        if (self._palatteModeAdaptive.get_active()):
            return Image.ADAPTIVE
        else:
            return Image.WEB
    
    def getPaletteColors(self):
        if self.getPaletteMode() == Image.WEB:
            return 216
        else:
            return self._colorPalette.get_value()
    
    def _onAllFileSelectClicked(self, event):
        self._fileSelect.set_inconsistent(False)
        selAll = self._fileSelect.get_active()
        
        if not selAll:
            self._btnFileProps.set_sensitive(False)
            self._btnFileRemove.set_sensitive(False)
            self._btnFileTurnL.set_sensitive(False)
            self._btnFileTurnR.set_sensitive(False)
            
        for f in self._mediaFiles.getFiles():
            cb = self._getFileSelectCheckBtn(f)
            if cb:
                cb.set_active(selAll)
        pass
    
    def _onAllFileSelectToggled(self, event):
        selAll = self._fileSelect.get_active()
        if not selAll:
            self._btnFileProps.set_sensitive(False)
            self._btnFileRemove.set_sensitive(False)
            self._btnFileTurnL.set_sensitive(False)
            self._btnFileTurnR.set_sensitive(False)
        pass
    
    def _onFileSelectClicked(self, event, file_name):
        selState = self.getFileSelectState()
        if selState['count'] == 0:
            self._fileSelect.set_sensitive(False)
            self._fileSelect.set_active(False)
            self._fileSelect.set_inconsistent(False)
        else:
            self._fileSelect.set_sensitive(True)
            self._fileSelect.set_active(selState['count'] == selState['selected'])
            self._fileSelect.set_inconsistent(selState['count'] != selState['selected'] and selState['selected'] > 0)
        pass
    
    def _onFileSelectToggle(self, event, file_name):
        selState = self.getFileSelectState()
        if selState['selected'] == 0:
            self._btnFileProps.set_sensitive(False)
            self._btnFileRemove.set_sensitive(False)
            self._btnFileTurnL.set_sensitive(False)
            self._btnFileTurnR.set_sensitive(False)
        else:
            self._btnFileProps.set_sensitive(True)
            self._btnFileRemove.set_sensitive(True)
            self._btnFileTurnL.set_sensitive(True)
            self._btnFileTurnR.set_sensitive(True)
        pass
    
    def _onBtnFilePropClicked(self, event):
        selState = self.getFileSelectState()
        if selState['selected'] == 0: return
        
        dialog = GtkTimeStampDialog(selState['files'], self.getModeFileDate())
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            self.doUpdateTable()
        pass
    
    
    def getFileSelectState(self):
        result = {'count':0, 'selected':0, 'files': []}
        cb = gtk.CheckButton
        n = 0
        sel = 0
        files = []
        for f in self._mediaFiles.getFiles():
            cb = self._getFileSelectCheckBtn(f)
            if cb:
                n += 1
                if cb.get_active():
                    sel += 1
                    files.append(f)
        
        result['count'] = n
        result['selected'] = sel
        result['files'] = files
        return result
    
    def _getFileSelectCheckBtn(self, file_name):
        w = self._mediaFiles.getData(file_name, 'widgets')
        if w:
            w = w['select']
            return w
        return None
        
    
    def _onPalatteModeChanged(self, widget):
        if (self._palatteModeAdaptive.get_active()):
            self._colorPalette.set_sensitive(True)
            self._colorPalette.set_value(256)
        else:
            self._colorPalette.set_sensitive(False)
            self._colorPalette.set_value(self.getPaletteColors())
        pass
    
    def _onScan(self, widget):
        if self._scanRunning:
            self._scanAbort = True
            return
        self._scanRunning = True
        
        self._radioBtnFileDate.set_sensitive(False)
        self._radioBtnMetaDate.set_sensitive(False)
        
        # init file list
        self._mediaFiles = MediaFiles()
        
        # get date filter
        self._filterOn = self._calActive.get_active()
        filterFrom = self._calFrom.get_date()
        filterTo = self._calTo.get_date()
        self._filterFrom = date(filterFrom[0], filterFrom[1] + 1, filterFrom[2])
        self._filterTo = date(filterTo[0], filterTo[1] + 1, filterTo[2])
        if self._filterTo < self._filterFrom:
            self._filterTo = self._filterFrom
        
        self._scanCount = 0
        srcFolder = self._srcFolder.get_filename().decode()
        
        self._scan.set_label("Cancel")
        if os.path.isdir(srcFolder):
            self._scanStatus.set_text("Folder scan...")
            self._srcFolderPath = srcFolder
            srcFiles = self._scanFolder(srcFolder)
            if srcFiles:
                def main_iter(mf, fname, n):
                    self._itemCount.set_text("%s (%s)" % (os.path.basename(fname), str(n)))
                    gtk.main_iteration()
                    if self._scanAbort:
                        return 1
                self._scanStatus.set_text("Import meta data...")
                self._mediaFiles = MediaFiles(srcFiles, main_iter)
        else:
            self._scan.set_label("Scan")
            self._scanStatus.set_text("No valid folder")
            self._itemCount.set_text("")
            self._srcFolderPath = None
        
        if not self._scanAbort:
            self._scanStatus.set_text("Add thumbs, item(s) listed:")
            # update image table
            self._updateThumbs()
            
            # update count
            self._scanStatus.set_text("Item(s) found:")
            self._itemCount.set_text(str(self._mediaFiles.count()))
        
        
        self._radioBtnFileDate.set_sensitive(True)
        self._radioBtnMetaDate.set_sensitive(True)
        
        self._scan.set_label("Scan")
        self._scanRunning = False
        self._scanAbort = False
    
    
    def _clearThumbTable(self, new_file_count=0):
        n = len(self._thumbTable.get_children())
        for c in self._thumbTable.get_children():
            if n <= 14: break
            self._thumbTable.remove(c)
            n -= 1
        # set to new size
        self._thumbTable.resize(new_file_count + 1, 13)
    
    
    def doUpdateTable(self, *args, **kwargs):
        if self._scanRunning:
            self._scanAbort = True
        
        self._radioBtnFileDate.set_sensitive(False)
        self._radioBtnMetaDate.set_sensitive(False)
        
        while self._scanRunning:
            gtk.main_iteration()
            time.sleep(0.1)
            
        self._scanRunning = True
        
        self._scan.set_label("Cancel")
        self._scanStatus.set_text("Update...")
        
        self._updateThumbs()
        
        self._radioBtnFileDate.set_sensitive(True)
        self._radioBtnMetaDate.set_sensitive(True)
        
        self._scan.set_label("Scan")
        self._scanStatus.set_text("Item(s) updated:")
        self._scanRunning = False
        self._scanAbort = False
    
    
    def _updateThumbs(self):
        # kill loading thread
        if self._loading_thread and self._loading_thread.isAlive():
            self._loading_thread.quit()
        # clear table
        self._clearThumbTable(self._mediaFiles.count())
        # preset
        row = 1
        i = 0
        images = []
        gtk_images = []
        # add files
        if self._mediaFiles.count() > 0:
            files = self._mediaFiles.getDateSorted(self.getModeFileDate())
            for f in files:
                if self._scanAbort: return
                # format file name
                fname = os.path.basename(f)
                s = f
                if (len(f) > 43):
                    if (len(fname) > 43):
                        s = "..." + fname[-40:]
                    else:
                        s = fname
                info = self._mediaFiles.getInfo(f)
                # date time info
                dt = info['datetime']
                # camera model
                model = ''
                if 'model' in info:
                    model = info['model'].replace('/', '\n')
                # build GUI elements
                selectBtn = gtk.CheckButton()
                selectBtn.set_focus_on_click(False)
                selectBtn.set_label('')
                selectBtn.connect('released', self._onFileSelectClicked, f)
                selectBtn.connect('toggled', self._onFileSelectToggle, f)
                linkbtn_fname = gtk.LinkButton(uri='file://' + f, label=s)
                linkbtn_fname.set_alignment(0, 0.5)
                linkbtn_fname.set_tooltip_text(f)
                thumb = gtk.Image()
                thumb.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
                
                # build tooltip window
                ttwin = gtk.Window(gtk.WINDOW_POPUP)
                ttim = gtk.Image()
                ttim.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_DIALOG)
                ttwin.add(ttim)
                ttloaderInfo = [f, ttim]
                ttim.show()
                thumb.set_tooltip_window(ttwin)
                thumb.props.has_tooltip = True
                # callback when tooltip window shown
                def queryTt(widget, x, y, keyboard_mode, tooltip, ttloaderInfo):
                    if len(ttloaderInfo) < 2:
                        return True
                    f = ttloaderInfo.pop(0)
                    im = ttloaderInfo.pop(0)
                    loader = wxAsyncImageLoader((300, 300), False)
                    loader.start([f], [im])
                    return True
                thumb.connect('query-tooltip', queryTt, ttloaderInfo)
                
                
                label_metadate = gtk.Label(dt.strftime("%a. %d. %b. '%y\n%H:%M:%S"))
                label_modifydate = gtk.Label(GetFileTimeStamp(f).strftime("%a. %d. %b. '%y\n%H:%M:%S"))
                label_model = gtk.Label(model)
                label_tz = gtk.Label()
                if dt.tzinfo != None:
                    label_tz.set_text(dt.strftime('%Z (%z)'))
                label_dim = gtk.Label('')
                if 'width' in info and 'height' in info:
                    label_dim.set_text('%s x %s' % (str(info['width']), str(info['height'])))
                gtk.main_iteration()
                
                self._thumbTable.attach(selectBtn, 0, 1, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)    
                self._thumbTable.attach(linkbtn_fname, 1, 2, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)
                self._thumbTable.attach(thumb, 3, 4, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)
                self._thumbTable.attach(label_metadate, 5, 6, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)
                self._thumbTable.attach(label_modifydate, 7, 8, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)
                self._thumbTable.attach(label_model, 9, 10, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)
                self._thumbTable.attach(label_tz, 11, 12, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)
                self._thumbTable.attach(label_dim, 13, 14, row, row + 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, xpadding=4, ypadding=4)
                selectBtn.show()
                linkbtn_fname.show()
                thumb.show()
                label_metadate.show()
                label_modifydate.show()
                label_model.show()
                label_tz.show()
                label_dim.show()
                # attach to media DB
                self._mediaFiles.attachData(f, 'widgets',
                                            {'select': selectBtn,
                                             'linkbtn_fname': linkbtn_fname,
                                             'label_metadate': label_metadate,
                                             'label_modifydate': label_modifydate,
                                             'label_model':label_model,
                                             'label_tz':label_tz}
                                            )
                # update UI
                gtk.main_iteration()
                # set item count
                i += 1
                self._itemCount.set_text(str(i) + "/" + str(self._mediaFiles.count()))
                # count columns
                row += 1
                
                # append to list
                images.append(f)
                gtk_images.append(thumb)
                
                # start thumb thread after 1/3 of image entries created
                if self._loading_thread == None or self._loading_thread.stopping():
                    # create thread, because its finished
                    self._loading_thread = wxAsyncImageLoader((50, 50), True, True)
                    self._loading_thread.start(images, gtk_images)
                    images = []
                    gtk_images = []
                elif self._loading_thread.append(images, gtk_images):
                    images = []
                    gtk_images = []
            # self._thumbTable.show_all()
        
        # update check box state
        self._onFileSelectClicked(None, None)
        pass
        
    
    def _scanFolder(self, folderPath):
        if os.path.isdir(folderPath):
            result = []
            dirl = []
            try:
                dirl = os.listdir(folderPath)
            except:
                pass
            for fname in dirl:
                if self._scanAbort: return result
                fpath = folderPath + os.path.sep + fname
                if os.path.isdir(fpath) and self._scanSubfolder.get_active():
                    # update GUI
                    l = len(fpath)
                    if (l > 23):
                        self._scanStatus.set_text("Folder scan ..." + fpath[-20:])
                    else:
                        self._scanStatus.set_text("Folder scan " + fpath)
                    self._itemCount.set_text(str(self._scanCount))
                    gtk.main_iteration()
                    # scan sub folder
                    sub = self._scanFolder(fpath)
                    if sub:
                        result.extend(sub)
                elif os.path.isfile(fpath) and IsSupportedFile(fpath):
                    # media found, check date
                    if (self._filterOn):
                        imageDate = GetMediaTimeStamp(fpath).date()
                        if (imageDate < self._filterFrom or imageDate > self._filterTo):
                            # image out of date range
                            continue
                    result.append(fpath)
                    self._scanCount += 1
                pass
            return result
        else:
            return None
    
    def quit(self, args=None):
        if self._loading_thread != None and not self._loading_thread.stopping():
            self._loading_thread.quit()
        gtk.main_quit()
        
        
        
class wxAsyncImageLoader(threading.Thread):

    def __init__(self, image_size=(50, 50), crop_image=True, cache=False):
        '''WxAsyncImageLoader(image_size=(50, 50), crop_image=True, cache=False)
            crop_image: cut image to requested size
            cache: store pixbuf data into file data base and try to reload from data base
        '''
        threading.Thread.__init__(self)
        if type(image_size) != tuple or len(image_size) != 2:
            raise ArgumentTypeError('Argument image_size must be tuple with 2 values for width and height (X, Y)')
        self._im_size = image_size
        self._crop = crop_image
        self._images = []
        if cache:
            self._mediaDb = MediaFiles()
        else:
            self._mediaDb = None
        self._gtk_images = []
        self._lock = thread.allocate_lock()
        self._quit = False
        self._has_stopped = False

    def stopping(self):
        self._lock.acquire()
        stopping = self._quit
        self._lock.release()
        return stopping
        
    def quit(self):
        self._quit = True
        self.join()
    
    def start(self, file_names=[], gtk_images=[]):
        self._images.extend(file_names)
        self._gtk_images.extend(gtk_images)
        return threading.Thread.start(self)

    def append(self, file_names=[], gtk_images=[]):
        '''append() --> boolean
        returns True, if elements are added to internal queue otherwise False.
        Thread must be running.
        '''
        if threading.Thread.isAlive(self) or not self._quit:
            self._lock.acquire()
            self._images.extend(file_names)
            self._gtk_images.extend(gtk_images)
            self._lock.release()
            return True
        return False

    def run(self):
        while not self._quit:
            self._lock.acquire()
            nextItem = len(self._images) > 0 and len(self._gtk_images) > 0
            if nextItem:
                f = self._images.pop(0)
                thumb = self._gtk_images.pop(0)
            self._lock.release()
            # leave the loop, when all items processed
            if not nextItem:
                break
            # check item
            if thumb == None or (not os.path.isfile(f)):
                continue
            # load the image
            try:
                im = None
                # check if image file is already cached
                if self._mediaDb:
                    if self._mediaDb.append(f):
                        im = self._mediaDb.getImage(f)
                        if im and ((im.size[0] < self._im_size[0]) or
                            (im.size[1] < self._im_size[1])):
                            im = None
                if im == None:
                    # load info from file
                    im = Image.open(f, 'r')
                    imOrientation = GetImageOrientation(f)
                    im = AutoOrientImage(im, imOrientation)
                if self._crop:
                    im = CropResize(im, self._im_size)
                else:
                    im = FitResize(im, self._im_size)
                # append to data base
                if self._mediaDb:
                    self._mediaDb.setImage(f, im)

                # convert to pixel buffer                    
                pb = GetGtkPixbuf(im)
                
                # check to exit
                if self._quit: break
                #gtk.threads_enter()
                try:
                    thumb.set_from_pixbuf(pb)
                except:
                    thumb.set_from_stock(gtk.STOCK_MISSING_IMAGE, gtk.ICON_SIZE_DIALOG)
                #gtk.threads_leave()
                # give other threads time
                time.sleep(0.1)
            except:
                sys.stderr.write('WxAsyncImageLoader: %s --> %s\n ' % (f, str(sys.exc_value)))
                thumb.set_from_stock(gtk.STOCK_MISSING_IMAGE, gtk.ICON_SIZE_DIALOG)
                pass
            pass
        
        # finish the thread
        self._quit = True
        self._has_stopped = True
    
def fitScale(width, height, max_width, max_height):
    '''fitScale() --> return: (width, height)'''
    
    if max_width == 0 or width == 0 or max_height == 0 or height == 0:
        return (0, 0)
    
    # calculate ratio
    sourceRatio = float(width) / float(height)
    targetRatio = float(max_width) / float(max_height)
    
    # Calculate X and Y size
    newWidth = max_width
    newHeight = max_height
    if sourceRatio > targetRatio:
        newHeight = float(newWidth) / sourceRatio
    elif sourceRatio < targetRatio:
        newWidth = float(newHeight) * sourceRatio
        pass
    
    # return new, scaled dimensions
    return (int(newWidth), int(newHeight))


def cropScale(width, height, new_width, new_height):
    '''cropScale() --> return: (scale, offsetX, offsetY)'''
    if new_height == 0 or new_width == 0:
        return (0, 0, 0)
    
    targetRatio = float(new_width) / float(new_height);
    
    # crop image to the target ratio
    srcX = float(width)
    srcY = float(height)
    sourceRatio = srcX / srcY;
    scale = 1.0
    
    if (sourceRatio >= targetRatio):
        scale = float(new_height) / srcY
        xSrcOffs = int((((srcY * targetRatio) - srcX) * scale) / 2.0)
        ySrcOffs = 0
    else:
        scale = float(new_width) / srcX
        xSrcOffs = 0
        ySrcOffs = int((((srcX / targetRatio) - srcY) * scale) / 2.0)
        pass
    
    return (scale, xSrcOffs, ySrcOffs)
    
    
def gtkCropScale(pixbuf, new_width, new_height):
    scale, offsX, offsY = cropScale(pixbuf.get_width(), pixbuf.get_height(), new_width, new_height)
    new_pixbuf = gtk.gdk.Pixbuf(pixbuf.get_colorspace(), pixbuf.get_has_alpha(), pixbuf.get_bits_per_sample(), new_width, new_height)
    pixbuf.scale(dest=new_pixbuf,
                 dest_x=0,
                 dest_y=0,
                 dest_width=new_width,
                 dest_height=new_height,
                 offset_x=offsX,
                 offset_y=offsY,
                 scale_x=scale,
                 scale_y=scale,
                 interp_type=gtk.gdk.INTERP_BILINEAR)
    return new_pixbuf


class GtkTimeStampDialog:

    def __init__(self, files=[], preset_fileTime=False):        
        self._xmlName = "gtkModifyTimeStamp.glade"
        self._xml = gtk.glade.XML(self._xmlName, 'dialog1')
        self._window = gtk.Dialog
        self._window = self._xml.get_widget("dialog1")
        self._notebook = gtk.Notebook
        self._notebook = self._xml.get_widget("notebook1")
        self._cbModifyFileTime = gtk.CheckButton
        self._cbModifyFileTime = self._xml.get_widget('checkbutton1')
        self._cbModifyFileTime.set_active(False)
        self._cbModifyFileTime.connect('clicked', self._checkApply)
        self._cbModifyEXIF = gtk.CheckButton
        self._cbModifyEXIF = self._xml.get_widget('checkbutton2')
        self._cbModifyEXIF.set_inconsistent(False)
        self._cbModifyEXIF.set_active(False)
        self._cbModifyEXIF.connect('clicked', self._checkApply)
        self._status = gtk.Label
        self._status = self._xml.get_widget('label13')
        self._calendar = gtk.Calendar
        self._calendar = self._xml.get_widget('calendar1')
        self._spinHour = gtk.SpinButton
        self._spinMinute = gtk.SpinButton
        self._spinSecond = gtk.SpinButton
        self._spinHour = self._xml.get_widget('spinbutton1')
        self._spinMinute = self._xml.get_widget('spinbutton2')
        self._spinSecond = self._xml.get_widget('spinbutton3')
        self._cbCountMs = gtk.CheckButton
        self._cbCountMs = self._xml.get_widget('checkbutton3')
        self._btnApplyDateTime = gtk.Button
        self._btnApplyTimeDelta = gtk.Button
        self._btnClose = gtk.Button
        self._btnApplyDateTime = self._xml.get_widget('button1')
        self._btnApplyTimeDelta = self._xml.get_widget('button3')
        self._btnClose = self._xml.get_widget('button2')
        self._btnClose.connect('clicked', self.close)
        self._btnApplyDateTime.connect('clicked', self._onBtnApplyDateTime)
        self._btnApplyTimeDelta.connect('clicked', self._onBtnApplyTimeDelta)
        self._modeFileTime = preset_fileTime
        self._progressDateTime = gtk.ProgressBar
        self._progressTimeDelta = gtk.ProgressBar
        self._progressDateTime = self._xml.get_widget('progressbar1')
        self._progressTimeDelta = self._xml.get_widget('progressbar2')
        
        self._spinDeltaWeeks = gtk.Spinner
        self._spinDeltaDays = gtk.Spinner
        self._spinDeltaHours = gtk.Spinner
        self._spinDeltaMinutes = gtk.Spinner
        self._spinDeltaSeconds = gtk.Spinner
        self._spinDeltaWeeks = self._xml.get_widget('spinbutton8')
        self._spinDeltaDays = self._xml.get_widget('spinbutton7')
        self._spinDeltaHours = self._xml.get_widget('spinbutton4')
        self._spinDeltaMinutes = self._xml.get_widget('spinbutton5')
        self._spinDeltaSeconds = self._xml.get_widget('spinbutton6')
        self._spinDeltaWeeks.connect('value-changed', self._checkApply)
        self._spinDeltaDays.connect('value-changed', self._checkApply)
        self._spinDeltaHours.connect('value-changed', self._checkApply)
        self._spinDeltaMinutes.connect('value-changed', self._checkApply)
        self._spinDeltaSeconds.connect('value-changed', self._checkApply)
        
        self._response = gtk.RESPONSE_CANCEL
        
        # callback when adding media files
        def _incr_callback(media_class, file_name, number):
            if not media_class.getData(file_name, 'hasEXIF'):
                self._cbModifyEXIF.set_inconsistent(True)
            else:
                self._cbModifyEXIF.set_active(True)                
                pass
            pass
        
        # import selected files
        self._media_db = MediaFiles(files, _incr_callback)
        
        # get first file time stamp
        self._files = self._media_db.getDateSorted()
        
        # check file count
        if self._media_db.count() > 0:
            self._btnApplyDateTime.set_sensitive(True)
            self._btnApplyTimeDelta.set_sensitive(True)
            self._cbModifyFileTime.set_active(True)
            self._cbModifyFileTime.set_sensitive(True)
            self._cbModifyEXIF.set_sensitive(True)
            dt = datetime.datetime
            if preset_fileTime:
                dt = self._media_db.getData(self._files[0], 'modified')
            else:
                dt = self._media_db.getData(self._files[0], 'datetime')
            self._calendar.select_month(dt.month - 1, dt.year)
            self._calendar.select_day(dt.day)
            self._calendar.mark_day(dt.day)
            self._spinHour.set_value(dt.hour)
            self._spinMinute.set_value(dt.minute)
            self._spinSecond.set_value(dt.second)
            self._cbCountMs.set_sensitive(self._media_db.count() > 1)
            self._status.set_text('%s item(s) selected.' % self._media_db.count())
        else:
            self._cbModifyFileTime.set_sensitive(False)
            self._cbModifyEXIF.set_sensitive(False)
            self._calendar.set_sensitive(False)
            self._calendar.set_sensitive(False)
            self._spinHour.set_sensitive(False)
            self._spinMinute.set_sensitive(False)
            self._spinSecond.set_sensitive(False)
            self._cbCountMs.set_sensitive(False)
            self._status.set_text('No items selected.')
            
        # control EXIF check button
        if not self._cbModifyEXIF.get_active():
            self._cbModifyEXIF.set_inconsistent(False)
        
    def _checkApply(self, *args, **kwargs):
        if ((not self._cbModifyEXIF.get_active() and
             not self._cbModifyFileTime.get_active()
             ) or self._media_db.count() == 0
            ):
            self._btnApplyDateTime.set_sensitive(False)
            self._btnApplyTimeDelta.set_sensitive(False)
        else:
            self._btnApplyDateTime.set_sensitive(True)
            
            if (self._spinDeltaWeeks.get_value() == 0.0 and
                self._spinDeltaDays.get_value() == 0.0 and
                self._spinDeltaHours.get_value() == 0.0 and
                self._spinDeltaMinutes.get_value() == 0.0 and
                self._spinDeltaSeconds.get_value() == 0.0
                ):
                self._btnApplyTimeDelta.set_sensitive(False)
            else:
                self._btnApplyTimeDelta.set_sensitive(True)
                pass
            pass
        return self._btnApplyDateTime.get_sensitive(), self._btnApplyTimeDelta.get_sensitive()
        
    def _onBtnApplyDateTime(self, event):
        exif = self._cbModifyEXIF.get_active() or self._cbModifyEXIF.get_inconsistent()
        fileTime = self._cbModifyFileTime.get_active()
        if not self._checkApply()[0]: return
        countMs = self._cbCountMs.get_active()
        # get calendar date
        year, month, day = self._calendar.get_date()
        # get time stamp
        dt = datetime.datetime(year=year,
                               month=month + 1,
                               day=day,
                               hour=int(self._spinHour.get_value()),
                               minute=int(self._spinMinute.get_value()),
                               second=int(self._spinSecond.get_value())
                               )
        # prepare progressbar
        progCnt = 0.0
        self._progressDateTime.set_value(progCnt)
        fileCnt = self._media_db.count()
        if fileCnt == 0: return
        progIncr = 100.0 / float(fileCnt)
        for f in self._media_db.getDateSorted(self._modeFileTime):
            self._progressDateTime.set_text(os.path.basename(f))
            if exif:
                SetEXIF(f, ['Exif', 'Image', 'DateTime'], dt)
            if fileTime:
                SetFileTimeStamp(f, dt)
            if countMs:
                dt += datetime.timedelta(milliseconds=1)
            progCnt += progIncr
            self._progressDateTime.set_value(progCnt)
            gtk.main_iteration()
            pass
        self._response = gtk.RESPONSE_OK
        self.close()
        pass
    
    
    def _onBtnApplyTimeDelta(self, event):
        exif = self._cbModifyEXIF.get_active() or self._cbModifyEXIF.get_inconsistent()
        fileTime = self._cbModifyFileTime.get_active()
        if not self._checkApply()[0]: return
        # get time delta
        td = datetime.timedelta(days=int(self._spinDeltaDays.get_value()),
                                seconds=int(self._spinDeltaSeconds.get_value()),
                                minutes=int(self._spinDeltaMinutes.get_value()),
                                hours=int(self._spinDeltaHours.get_value()),
                                weeks=int(self._spinDeltaWeeks.get_value()))
        
        # prepare progressbar
        progCnt = 0.0
        self._progressTimeDelta.set_value(progCnt)
        fileCnt = self._media_db.count()
        if fileCnt == 0: return
        progIncr = 100.0 / float(fileCnt)
        for f in self._media_db.getDateSorted(self._modeFileTime):
            self._progressTimeDelta.set_text(os.path.basename(f))
            info = self._media_db.getInfo(f)
            fdt = GetFileTimeStamp(f)
            if exif and 'hasEXIF' in info:
                dt = info['datetime'] + td
                if SetEXIF(f, ['Exif', 'Image', 'DateTime'], dt):
                    info['datetime'] = dt
            if fileTime:
                dt = fdt + td
                SetFileTimeStamp(f, dt)
            progCnt += progIncr
            self._progressTimeDelta.set_value(progCnt)
            gtk.main_iteration()
            pass
        self._response = gtk.RESPONSE_OK
        self.close()
        pass
        
        
    def run(self):
        self._window.run()
        return self._response
    
    
    def close(self, *args, **kwargs):
        self._window.destroy()
        pass


if __name__ == '__main__':
    
    gtkWin = GtkImageAppBase()
    gtkWin.show()

